#include <windows.h>
#include <sqlext.h>
#include <odbcinst.h>		//for ODBC_BOTH_DSN

#include <stdio.h>

#include "op_odbc_setup.h"
#include "op_string.h"			

/*!
\note   - unixODBC does not have a header section in ini files.
- On XP, the "32 bit" parts are secretly stripped away
inside SQLGetPrivateProfileString() somewhere.
*/            
#define OPODBCUTIL_ODBCINI_HEADER_SECTION "ODBC Data Sources"

/*!
\internal
\brief      Allocate and return a odbc_data_source_t.

Allocates, initializes and returns a odbc_data_source_t
structure.
*/  
odbc_data_source_t *odbc_alloc_data_source(odbc_data_source_mode_t mode)
{
	odbc_data_source_t *p =
		(odbc_data_source_t*)malloc(sizeof(odbc_data_source_t));

	memset(p, 0, sizeof(odbc_data_source_t));
	p->mode = mode;
	p->connect = OPODBCUTIL_DATASOURCE_CONNECT_DRIVER;
	p->prompt = OPODBCUTIL_DATASOURCE_PROMPT_NOPROMPT;

	return p;
}

/*!
\brief  Read data source from system information.

\param  data_source Data Source struct.
\param  dsn_name The data source name to lookup.

\note   This will not replace existing values in data_source.

On XP we use hard-coded driver name and driver
file name (if not given) as it seems MS is in the middle
of changing odbc system info (probably to better support
64 bit).
*/            
BOOL odbc_read_data_source(odbc_data_source_t *data_source, LPCSTR dsn_name)
{
	char    sz_entry_names[SQL_MAX_DSN_LENGTH * OPODBCUTIL_MAX_DSN_NAMES];
	char *  sz_entry_name;
	char    sz_value[4096];
	int     ch;
#ifdef _WIN32
	UWORD   mode   = ODBC_BOTH_DSN;
#endif
	SAVE_MODE();

#ifdef _WIN32
	if (!SQLGetConfigMode(&mode)) {
		fprintf(stderr, 
			"[%s][%d][ERROR] SQLGetConfigMode failed!\n", 
			__FILE__, 
			__LINE__);
		return FALSE;
	}
#endif

	if (!dsn_name || !(*dsn_name))
		return TRUE;

	*sz_entry_names = '\0';

	/* Get the list of key names for the DSN's section. */
	ch = SQLGetPrivateProfileString(dsn_name, 
		NULL, 
		"",
		sz_entry_names, 
		sizeof(sz_entry_names) - 1,
		"ODBC.INI");
	if (ch < 1)
		return FALSE;

	RESTORE_MODE();

#if defined(WIN32)
	{
		/*
		There is a bug in SQLGetPrivateProfileString when mode is
		ODBC_BOTH_DSN and we are looking for a system DSN. In this case
		SQLGetPrivateProfileString will find the system DSN but return
		a corrupt list of attributes.

		A corrupt list of attributes can be identified because the first
		attribute (if any) will be followed by more than one '\0'.

		The solution is to detect this condition and set mode to
		ODBC_SYSTEM_DSN and try again. We also ensure we reset the mode
		when done - regardless of outcome.

		This may be the issue explained here:
		http://support.microsoft.com/kb/909122/

		And this work-around may not be correct:
		http://bugs.mysql.com/27599
		*/
		int len = strlen(sz_entry_names);

		if (mode == ODBC_BOTH_DSN && len < ch && 
			sz_entry_names[len + 1 ] == '\0') {
			*sz_entry_names = '\0';
			if (!SQLSetConfigMode(ODBC_SYSTEM_DSN))
				fprintf(stderr, 
				"[%s][%d][ERROR] SQLSetConfigMode failed!\n",
				__FILE__, 
				__LINE__);

			if ((ch = SQLGetPrivateProfileString(dsn_name, 
				NULL, 
				NULL, 
				sz_entry_names,
				sizeof(sz_entry_names) - 1, 
				"ODBC.INI")) < 1) {
				return FALSE;
			}				
		}
	}
#endif

	/* Looks like things are going to work. Lets set DSN.  */
	if (!data_source->dsn_name)
		data_source->dsn_name = _global_strdup(dsn_name);

	/* Handle each key we found for this DSN */
	sz_entry_name = sz_entry_names;
	while (*sz_entry_name) {
		*sz_value = '\0';
		if (SQLGetPrivateProfileString(dsn_name,
			sz_entry_name,
			"",
			sz_value,
			sizeof(sz_value) - 1, 
			"ODBC.INI") > 0) {
			/*!
			ODBC RULE
			(SQLDriverConnect)

			The driver uses any information it retrieves from the system information 
			to augment the information passed to it in the connection string. If the 
			information in the system information duplicates information in the 
			connection string, the driver uses the information in the connection 
			string.
			*/    
			if (_stricmp(sz_entry_name, "DATABASE") == 0 ||
				_stricmp(sz_entry_name, "DB") == 0) {
				if (!data_source->default_db)
					data_source->default_db = _global_strdup(sz_value) ;
			} else if (_stricmp(sz_entry_name, "DESCRIPTION") == 0 || 
				_stricmp(sz_entry_name, "DESC") == 0) {
				if (!data_source->dsn_desc)
					data_source->dsn_desc = _global_strdup(sz_value);
			} else if (_stricmp(sz_entry_name, "DRIVER") == 0) {
#if defined(WIN32)
				if (!data_source->drv_file_name)
					data_source->drv_file_name = _global_strdup(sz_value);
#else
				if (*sz_value == '/') {
					if (!data_source->drv_file_name)
						data_source->drv_file_name = _global_strdup(sz_value);
				} else {
					if (!data_source->driver_name)
						data_source->driver_name = _global_strdup(sz_value);
				}
#endif
			} else if (_stricmp(sz_entry_name, "OPTION") == 0) {    
				if (!data_source->option)
					data_source->option = _global_strdup(sz_value);
			} else if (_stricmp(sz_entry_name, "PWD") == 0 ||
				_stricmp(sz_entry_name, "PASSWORD") == 0) {    
				if (!data_source->passwd)
					data_source->passwd = _global_strdup(sz_value);
			} else if (_stricmp(sz_entry_name, "PORT") == 0) {    
				if (!data_source->srv_port)
					data_source->srv_port = _global_strdup(sz_value);
			} else if (_stricmp(sz_entry_name, "SERVER") == 0) {    
				if (!data_source->srv_name)
					data_source->srv_name = _global_strdup(sz_value);
			} else if (_stricmp(sz_entry_name, "SOCKET") == 0) {    
				if (!data_source->sock_name)
					data_source->sock_name = _global_strdup(sz_value);
			} else if (_stricmp(sz_entry_name, "STMT") == 0) {    
				if (!data_source->stmt)
					data_source->stmt = _global_strdup(sz_value);
			} else if (_stricmp(sz_entry_name, "UID") == 0 || 
				_stricmp(sz_entry_name, "USER") == 0) {    
				if (!data_source->user_name)
					data_source->user_name = _global_strdup(sz_value);
			} /* 
			else if (_stricmp(sz_entry_name, "SSLCA") == 0) {    
				if (!data_source->ssl_cert_auth_file)
					data_source->ssl_cert_auth_file = _global_strdup(sz_value);
			} else if (_stricmp(sz_entry_name, "SSLCAPATH") == 0) {    
				if (!data_source->ssl_cert_auth_file_path)
					data_source->ssl_cert_auth_file_path = _global_strdup(sz_value);
			} else if (_stricmp(sz_entry_name, "SSLCERT") == 0) {    
				if (!data_source->ssl_cert)
					data_source->ssl_cert = _global_strdup(sz_value);
			} else if (_stricmp(sz_entry_name, "SSLCIPHER") == 0) {    
				if (!data_source->ssl_cipher)
					data_source->ssl_cipher = _global_strdup(sz_value);
			} else if (_stricmp(sz_entry_name, "SSLKEY") == 0) {    
				if (!data_source->ssl_key)
					data_source->ssl_key = _global_strdup(sz_value);
			} else if (_stricmp(sz_entry_name, "SSLVERIFY") == 0) {    
				if (!data_source->ssl_verify)
					data_source->ssl_verify = _global_strdup(sz_value);
			} */else if (_stricmp(sz_entry_name, "CHARSET") == 0) {
				if (!data_source->charset)
					data_source->charset= _global_strdup(sz_value);
			} else if (_stricmp(sz_entry_name, "READTIMEOUT") == 0) {
				if (!data_source->read_timeout)
					data_source->read_timeout= _global_strdup(sz_value);
			} else if (_stricmp(sz_entry_name, "WRITETIMEOUT") == 0) {
				if (!data_source->write_timeout)
					data_source->write_timeout= _global_strdup(sz_value);
			} else {
				/* What the ? */
				fprintf(stderr, "[%s][%d][ERROR] Unknown attribute (%s).\n", 
					__FILE__, 
					__LINE__, 
					sz_entry_name);
			}
		} else {
			/*  
			At least two diff Users stated they did not want this message and I think we can do with out
			this one so lets silently move on.

			see; CSC 3985, email 52189

			fprintf(stderr, "[%s][%d][WARNING] Failed to get value for attribute (%s).\n", __FILE__, __LINE__, sz_entry_name);
			*/    
		}

		RESTORE_MODE();

		sz_entry_name += strlen(sz_entry_name) + 1;
	} /* while */

	/* try harder to get the friendly driver name */
	if (!data_source->driver_name) {
		if (SQLGetPrivateProfileString(OPODBCUTIL_ODBCINI_HEADER_SECTION, 
			"", 
			"", 
			sz_entry_names,
			sizeof(sz_entry_names) - 1, 
			"ODBC.INI") < 1) {
			/*return FALSE*/;
		}

		RESTORE_MODE();

		sz_entry_name = sz_entry_names;
		while (*sz_entry_name) {
			*sz_value = '\0';
			if (SQLGetPrivateProfileString(OPODBCUTIL_ODBCINI_HEADER_SECTION, 
				sz_entry_name, 
				"", 
				sz_value,
				sizeof(sz_value) - 1, 
				"ODBC.INI") > 0) {
				if (_stricmp(sz_entry_name, dsn_name) == 0) {
					data_source->driver_name = _global_strdup(sz_value);
				}
			}
			RESTORE_MODE();
			sz_entry_name += strlen(sz_entry_name) + 1;
		}
	}

#if defined(WIN32)
	/*!
	XP

	Some people have reported that the friendly driver name is never found so...
	*/
	if (!data_source->driver_name)
		data_source->driver_name = _global_strdup(OPODBCINST_DRIVER_NAME);
#endif

#if defined(WIN32)
	/* ALWAYS restore mode */
	SQLSetConfigMode(mode);
#endif

	return TRUE;
}

/*!
\brief    Free memory used by MYODBCUTIL_DATASOURCE.
*/  
void odbc_free_data_source(odbc_data_source_t *data_source)
{
	if (!data_source)
		return;

	odbc_clear_data_source(data_source);
	if (data_source->drv_file_name)
		_global_free(data_source->drv_file_name);
	if (data_source->driver_name)
		_global_free(data_source->driver_name);

	free(data_source);
}

/*!
\brief    Reinitializes MYODBCUTIL_DATASOURCE.

\note     Does NOT (read; MUST NOT) reinit drv_file_name, driver_name, 
mode, connect, and prompt.

*/
void odbc_clear_data_source(odbc_data_source_t *data_source)
{
	if (!data_source)
		return;

	if (data_source->default_db) {
		_global_free(data_source->default_db);
		data_source->default_db = NULL;
	} if (data_source->dsn_desc) {
		_global_free(data_source->dsn_desc);
		data_source->dsn_desc = NULL;
	} if (data_source->dsn_name) {
		_global_free(data_source->dsn_name);
		data_source->dsn_name = NULL;
	} if (data_source->option) {
		_global_free(data_source->option);
		data_source->option = NULL;
	} if (data_source->passwd) {
		_global_free(data_source->passwd);
		data_source->passwd = NULL;
	} if (data_source->srv_port) {
		_global_free(data_source->srv_port);
		data_source->srv_port = NULL;
	} if (data_source->srv_name) {
		_global_free(data_source->srv_name);
		data_source->srv_name = NULL;
	}
	if (data_source->sock_name) {
		_global_free(data_source->sock_name);
		data_source->sock_name = NULL;
	}
	if (data_source->stmt) {
		_global_free(data_source->stmt);
		data_source->stmt = NULL;
	}
	if (data_source->user_name) {
		_global_free(data_source->user_name);
		data_source->user_name = NULL;
	}
	if (data_source->read_timeout) {
		_global_free(data_source->read_timeout);
		data_source->read_timeout = NULL;
	}
	if (data_source->write_timeout) {
		_global_free(data_source->write_timeout);
		data_source->write_timeout = NULL;
	}
	/*
	if(data_source->ssl_cert_auth_file) {
		_global_free (data_source->ssl_cert_auth_file);
		data_source->ssl_cert_auth_file = NULL;
	}
	if(data_source->ssl_cert_auth_file_path) {
		_global_free (data_source->ssl_cert_auth_file_path);
		data_source->ssl_cert_auth_file_path = NULL;
	}
	if(data_source->ssl_cert) {
		_global_free (data_source->ssl_cert);
		data_source->ssl_cert = NULL;
	}
	if(data_source->ssl_cipher) {
		_global_free (data_source->ssl_cipher);
		data_source->ssl_cipher = NULL;
	}
	if(data_source->ssl_verify) {
		_global_free (data_source->ssl_verify);
		data_source->ssl_verify = NULL;
	}
	if(data_source->ssl_key) {
		_global_free (data_source->ssl_key);
		data_source->ssl_key = NULL;
	} */
	if (data_source->charset) {
		_global_free(data_source->charset);
		data_source->charset= NULL;
	}

}


/*!
\brief  Set NULL values to default value.

\param  data_source Data Source struct.

\note   The default values have been gleaned from pre-existing 
code in connect.c but there are some inconsistencies
between SQLConnect and SQLDriverConnect so I assume that
openPlant_real_connect is smarter than it used to be.
This needs to be cleaned up such that it reflects what is
really needed.
*/            
BOOL odbc_default_data_source(odbc_data_source_t *data_source)
{
	if (data_source->srv_name && !data_source->srv_name[0]) {
		_global_free(data_source->srv_name);
		data_source->srv_name = NULL;
	}

	if (data_source->default_db && !data_source->default_db[0]) {
		_global_free(data_source->default_db);
		data_source->default_db = NULL;
	}

	if (data_source->user_name && !data_source->user_name[0]) {
		_global_free(data_source->user_name);
		data_source->user_name = NULL;
	}

	if (data_source->passwd && !data_source->passwd[0]) {
		_global_free(data_source->passwd);
		data_source->passwd = NULL;
	}

	if (!data_source->srv_port)
		data_source->srv_port = _global_strdup("0");

	if (!data_source->option)
		data_source->option = _global_strdup("0");
	/*
	if(data_source->ssl_cert_auth_file && !data_source->ssl_cert_auth_file[0]) {
		_global_free (data_source->ssl_cert_auth_file);
		data_source->ssl_cert_auth_file = NULL;
	}
	if(data_source->ssl_cert_auth_file_path && !data_source->ssl_cert_auth_file_path[0]) {
		_global_free (data_source->ssl_cert_auth_file_path);
		data_source->ssl_cert_auth_file_path = NULL;
	}
	if(data_source->ssl_cert && !data_source->ssl_cert[0]) {
		_global_free (data_source->ssl_cert);
		data_source->ssl_cert = NULL;
	}
	if(data_source->ssl_cipher && !data_source->ssl_cipher[0]) {
		_global_free (data_source->ssl_cipher);
		data_source->ssl_cipher = NULL;
	}
	if(data_source->ssl_key && !data_source->ssl_key[0]) {
		_global_free (data_source->ssl_key);
		data_source->ssl_key = NULL;
	}
	if(data_source->ssl_verify && !data_source->ssl_verify[0]) {
		_global_free (data_source->ssl_verify);
		data_source->ssl_verify = NULL;
	} */
	if (data_source->charset && !data_source->charset[0]) {
		_global_free(data_source->charset);
		data_source->charset= NULL;
	}

#ifndef _UNIX_
	/* Here we actually unset socket for non-UNIX as it does not apply. */
	if (data_source->sock_name && !data_source->sock_name[0]) {
		free(data_source->sock_name);
		data_source->sock_name = NULL;
	}
#endif
	return TRUE;
}


/*!
\internal
\brief      Put a list of installed drivers in buffer.

\note       OSX

SQLGetPrivateProfileString() is broken but fortunately
the old GetPrivateProfileString() is available.

\note       XP

SQLGetPrivateProfileString() with a NULL 1st arg does
not return anything - ever. To return a list of drivers
we can use SQLGetInstalledDrivers() instead.                
*/  
BOOL odbc_get_driver_names(char *buffer, int n_buffer)
{
	int     ch = 0;
	SAVE_MODE();

	/*
	sanity check
	*/    
	if (!buffer || n_buffer < 1024) {
		fprintf(stderr, 
			"[%s][%d][ERROR] Insufficient buffer size. Please provide 1k or better yet - 32k.\n", 
			__FILE__, 
			__LINE__);
		return FALSE;
	}

#if defined(WIN32)
	ch = (SQLGetInstalledDrivers(buffer, n_buffer - 1, NULL) ? 1 : 0);
#else
	ch = SQLGetPrivateProfileString(NULL, NULL, "", buffer, n_buffer - 1, "ODBCINST.INI");
#endif
	if (ch < 1) {
		fprintf(stderr, 
			"[%s][%d][INFO] Call returned no data. Could be an error or just no data to return.\n", 
			__FILE__, 
			__LINE__);
		return FALSE;
	}

	RESTORE_MODE();

	return TRUE;
}


/*!
\internal
\brief      Allocate and return a MYODBCUTIL_DRIVER.

Allocates, initializes and returns a MYODBCUTIL_DRIVER
structure.
*/  
odbc_driver_t *odbc_alloc_driver()
{
	odbc_driver_t *p = (odbc_driver_t*)malloc(sizeof(odbc_driver_t));

	memset(p, 0, sizeof(odbc_driver_t));

	return p;
}

/*!
\brief  Read driver from system information.

\param  driver     Driver struct.
\param  name     User friendly name of driver such as "openPlant ODBC 3.51 Driver (32 bit)"
\param  file_name File name such as value found in DSN->Driver; "/usr/lib/libopodbc3.dylib"

\note   This will not replace existing values in driver.
*/            
BOOL odbc_read_driver(odbc_driver_t *driver, LPCSTR name, LPCSTR file_name)
{
	char    szSectionNames[SQL_MAX_DSN_LENGTH * OPODBCUTIL_MAX_DSN_NAMES];
	char *  pszSectionName  = NULL;
	char    sz_driverName[SQL_MAX_DSN_LENGTH + 1];
	char    sz_entry_names[SQL_MAX_DSN_LENGTH * OPODBCUTIL_MAX_DSN_NAMES];
	char *  sz_entry_name    = NULL;
	char    sz_value[4096];
	SAVE_MODE();

	/*
	* Ensure that we have the friendly name of the driver...
	*
	*/
	if (name && *name) {
		/*
		* Ensure that driver name is not enclosed in {} which prevents
		* information lookup with SQLGetPrivateProfileString().
		*/
		strncpy_s(sz_driverName, name, SQL_MAX_DSN_LENGTH + 1);
		sz_driverName[SQL_MAX_DSN_LENGTH]= 0;
		pszSectionName = sz_driverName;

		if (pszSectionName[0] == '{')
			pszSectionName++;
		if (pszSectionName[strlen(pszSectionName) - 1] == '}')
			pszSectionName[strlen(pszSectionName) - 1]= 0;
	} else if (file_name && *file_name) {
		/* get list of sections (friendly driver names)... */
		if (!odbc_get_driver_names(szSectionNames, sizeof(szSectionNames)))
			return FALSE;

		RESTORE_MODE();

		/* get value of DRIVER entry... */
		pszSectionName = szSectionNames;
		while (*pszSectionName) {
			if (SQLGetPrivateProfileString(pszSectionName, 
				"DRIVER", 
				"", 
				sz_value, 
				sizeof(sz_value) - 1, 
				"ODBCINST.INI") > 0) {
				RESTORE_MODE();
				if (strcmp(sz_value, file_name) == 0)
					break;
			}

			RESTORE_MODE();

			pszSectionName += strlen(pszSectionName) + 1;
		} /* while */
	}

	/*
	* No friendly driver name - no joy!
	*
	*/
	if (!pszSectionName)
		return FALSE;

#if defined(__APPLE__)
	if (SQLGetPrivateProfileString(pszSectionName,
		"", 
		"", 
		sz_entry_names,
		sizeof(sz_entry_names) - 1,
		"ODBCINST.INI") < 1)
#else
	if (SQLGetPrivateProfileString(pszSectionName,
		NULL,
		NULL, 
		sz_entry_names,
		sizeof(sz_entry_names) - 1,
		"ODBCINST.INI") < 1)
#endif
	{
		return FALSE;
	}

	RESTORE_MODE();

	sz_entry_name = sz_entry_names;
	while (*sz_entry_name) {
		*sz_value = '\0';

		if (SQLGetPrivateProfileString(pszSectionName,
			sz_entry_name,
			"",
			sz_value,
			sizeof(sz_value) - 1,
			"ODBCINST.INI") > 0) {
			if (_stricmp(sz_entry_name, "DRIVER") == 0) {
				if (!driver->driver_name)
					driver->driver_name = _global_strdup(sz_value) ;
			} else if (_stricmp(sz_entry_name, "SETUP") == 0) {
				if (!driver->pszSETUP)
					driver->pszSETUP = _global_strdup(sz_value);
			} else {
				/* What the ? */
			}
		}

		RESTORE_MODE();

		sz_entry_name += strlen(sz_entry_name) + 1;
	} /* while */

	/*
	* Success!
	*
	*/
	if (!driver->name)
		driver->name = _global_strdup(pszSectionName);

	return TRUE;
}

void odbc_clear_driver(odbc_driver_t *driver)
{
	if (!driver)
		return;

	if (driver->driver_name) {
		_global_free(driver->driver_name);
		driver->driver_name = NULL;
	} if (driver->name) {
		_global_free(driver->name);
		driver->name = NULL;
	} if (driver->pszSETUP) {
		_global_free(driver->pszSETUP);
		driver->pszSETUP = NULL;
	}
}

void odbc_free_driver(odbc_driver_t *driver)
{
	if (!driver)
		return;

	odbc_clear_driver(driver);

	free(driver);
}


/*!
\internal
\brief      Put a list of existing data source names in buffer.

\note       OSX

SQLGetPrivateProfileString() is broken but fortunately
the old GetPrivateProfileString() is available and seems
to work fine.

\note       XP

SQLGetPrivateProfileString() with a NULL 1st arg does
not return anything - ever. This is contrary to the
specification :(

Using SQLDataSources() is not a real option here as we
would need to be calling into the DM for that and we
are supposed to be below the DM in the depend hierarchy.

So we use the deprecated precursor to 
SQLGetPrivateProfileString().

GetPrivateProfileString() is not aware of changes to the way
ODBC system information is stored so we get stuff like
"ODBC 32 bit Data Sources" list as a datasource. It also
appears to be unaware of User vs System DSN. 
*/  
BOOL odbc_get_data_source_names(char *buffer, int n_buffer, UWORD scope)
{
	int ch = 0;

	/*
	sanity check
	*/    
	if (!buffer || n_buffer < 1024) {
		fprintf(stderr, 
			"[%s][%d][ERROR] Insufficient buffer size. Please provide 1k or better yet - 32k.\n", 
			__FILE__, 
			__LINE__);
		return FALSE;
	}

	/* set scope */
	switch (scope) {
	case ODBC_BOTH_DSN:
		break;
	case ODBC_USER_DSN:
	case ODBC_SYSTEM_DSN:
		if (!SQLSetConfigMode(scope))
			return FALSE;
		break;
	default:
		return FALSE;
	}

#if defined(WIN32)
	/* 
	This returns no data as does having a NULL for 1st arg!?

	ch = SQLGetPrivateProfileString("ODBC 32 bit Data Sources", NULL, NULL, buffer, n_buffer - 1, "ODBC.INI"); 
	*/
	/*
	This returns our data sources but includes "ODBC 32 bit Data Sources" as a data sources
	and does not respect config mode. Probably best until we use registery (MS is getting sloppy
	in supporting SQLGetPrivateProfileString).
	*/    
	ch = GetPrivateProfileString(NULL, NULL, NULL, buffer, n_buffer - 1, "ODBC.INI");
#else
	ch = SQLGetPrivateProfileString(NULL, NULL, "", buffer, n_buffer - 1, "ODBC.INI");
#endif

	/* unset scope */
	switch (scope) {
	case ODBC_BOTH_DSN:
		break;
	case ODBC_USER_DSN:
	case ODBC_SYSTEM_DSN:
		SQLSetConfigMode(ODBC_BOTH_DSN);
		break;
	}

	if (ch < 1) {
		fprintf(stderr, 
			"[%s][%d][INFO] Call returned no data. Could be an error or just no data to return.\n", 
			__FILE__, 
			__LINE__);
		return FALSE;
	}

	return TRUE;
}


BOOL odbc_read_data_source_str_val_term(odbc_delim_t delim, char ch)
{
	switch (delim) {
	case OPODBCUTIL_DELIM_NULL:
		/* Terminator is a '\0'. */
		if (ch == '\0')
			return TRUE;
		break;
	case OPODBCUTIL_DELIM_SEMI:
		/* Terminator is a ';'.                                                     */
		/* We check for '\0' to handle case where last value does not have a ';'.   */
		/* In such a case the '\0' is the string terminator. This makes us          */
		/* effectively same as MYODBCUTIL_DELIM_BOTH.                               */
		if (ch == ';' || ch == '\0')
			return TRUE;
		break;
	case OPODBCUTIL_DELIM_BOTH:
		/* Terminator can be either ';' or '\0'. */
		if (ch == ';' || ch == '\0')
			return TRUE;
		break;
	}

	return FALSE;
}

/*!
\internal
\brief      Parse a string of "name=value" pairs.

ConfigDSN receives connection information from the installer DLL 
as a list of attributes in the form of keyword-value pairs. 

\note       This will not replace existing values in data_source.
*/  
BOOL odbc_read_data_source_str(odbc_data_source_t *data_source, odbc_delim_t delim, LPCSTR str)
{
	odbc_attr_parse_state_t state         = OPODBCUTIL_ATTR_PARSE_STATE_NAME_START;
	char *                  anchor_char    = (char *)str;
	char *                  scan_char      = (char *)str;
	char *                  name        = 0;     

	/* short circuit if we have not been given stuff to parse */
	if (!str || !(*str))
		return TRUE; /* done and no errors :) */

	/* scan the input */
	while (1) {
		switch (state) {
		case OPODBCUTIL_ATTR_PARSE_STATE_NAME_START: {
				if (isalpha(*scan_char)) {
					anchor_char = scan_char;
					state = OPODBCUTIL_ATTR_PARSE_STATE_NAME; 
				}
			}
			break;
		case OPODBCUTIL_ATTR_PARSE_STATE_NAME: {
				if ((!isalpha(*scan_char) && !isdigit(*scan_char)) || *scan_char == '=') {
					name = (char *)_global_strndup(anchor_char, scan_char - anchor_char);

					if (*scan_char == '=')
						state = OPODBCUTIL_ATTR_PARSE_STATE_VALUE_START;
					else
						state = OPODBCUTIL_ATTR_PARSE_STATE_EQUAL;       
				}
			}
			break;
		case OPODBCUTIL_ATTR_PARSE_STATE_EQUAL: {
				if (*scan_char == '=')
					state = OPODBCUTIL_ATTR_PARSE_STATE_VALUE_START;
			}
			break;
		case OPODBCUTIL_ATTR_PARSE_STATE_VALUE_START: {
				if (!isspace(*scan_char)) {
					anchor_char = scan_char;
					state = OPODBCUTIL_ATTR_PARSE_STATE_VALUE;
				}
			}
			break;
		case OPODBCUTIL_ATTR_PARSE_STATE_VALUE: {
				if (odbc_read_data_source_str_val_term(delim, *scan_char)) {
					if (_stricmp(name, "DATABASE") == 0 || _stricmp(name, "DB") == 0) {
						if (!data_source->default_db)
							data_source->default_db = 
							(char *)_global_strndup(anchor_char, scan_char - anchor_char) ;
					} else if (_stricmp(name, "DESCRIPTION") == 0 || _stricmp(name, "DESC") == 0) {
						if (!data_source->dsn_desc)
							data_source->dsn_desc = 
							(char *)_global_strndup(anchor_char, scan_char - anchor_char);
					} else if (_stricmp(name, "DRIVER") == 0) {
						if (!data_source->driver_name)
							data_source->driver_name =
							(char *)_global_strndup(anchor_char, scan_char - anchor_char);
					} else if (_stricmp(name, "DSN") == 0) {
						if (!data_source->dsn_name)
							data_source->dsn_name = 
							(char *)_global_strndup(anchor_char, scan_char - anchor_char);
					} else if (_stricmp(name, "OPTION") == 0) {
						if (!data_source->option)
							data_source->option =
							(char *)_global_strndup(anchor_char, scan_char - anchor_char);
					}
					/*
					OPODBC RULE

					We will 'read' variants on the standard keywords (but only write standard version).
					*/
					else if (_stricmp(name, "PWD") == 0 || _stricmp(name, "PASSWORD") == 0) {
						if (!data_source->passwd)
							data_source->passwd = 
							(char *)_global_strndup(anchor_char, scan_char - anchor_char);
					} else if (_stricmp(name, "PORT") == 0) {
						if (!data_source->srv_port)
							data_source->srv_port = 
							(char *)_global_strndup(anchor_char, scan_char - anchor_char);
					} else if (_stricmp(name, "SERVER") == 0) {
						if (!data_source->srv_name)
							data_source->srv_name = 
							(char *)_global_strndup(anchor_char, scan_char - anchor_char);
					} else if (_stricmp(name, "SOCKET") == 0) {
						if (!data_source->sock_name)
							data_source->sock_name = 
							(char *)_global_strndup(anchor_char, scan_char - anchor_char);
					} else if (_stricmp(name, "STMT") == 0) {
						if (!data_source->stmt)
							data_source->stmt = 
							(char *)_global_strndup(anchor_char, scan_char - anchor_char);
					}
					/*
					OPODBC RULE

					We will 'read' variants on the standard keywords (but only write standard version).
					*/
					else if (_stricmp(name, "UID") == 0 || _stricmp(name, "USER") == 0) {
						if (!data_source->user_name)
							data_source->user_name = (char *)_global_strndup(anchor_char, scan_char - anchor_char);
					} /*else if (_stricmp(name, "SSLCA") == 0) {
						if (!data_source->ssl_cert_auth_file)
							data_source->ssl_cert_auth_file = (char *)_global_strndup(anchor_char, scan_char - anchor_char);
					} else if (_stricmp(name, "SSLCAPATH") == 0) {
						if (!data_source->ssl_cert_auth_file_path)
							data_source->ssl_cert_auth_file_path = (char *)_global_strndup(anchor_char, scan_char - anchor_char);
					} else if (_stricmp(name, "SSLCERT") == 0) {
						if (!data_source->ssl_cert)
							data_source->ssl_cert = (char *)_global_strndup(anchor_char, scan_char - anchor_char);
					} else if (_stricmp(name, "SSLCIPHER") == 0) {
						if (!data_source->ssl_cipher)
							data_source->ssl_cipher = (char *)_global_strndup(anchor_char, scan_char - anchor_char);
					} else if (_stricmp(name, "SSLKEY") == 0) {
						if (!data_source->ssl_key)
							data_source->ssl_key = (char *)_global_strndup(anchor_char, scan_char - anchor_char);
					} else if (_stricmp(name, "SSLVERIFY") == 0) {
						if (!data_source->ssl_verify)
							data_source->ssl_verify = (char *)_global_strndup(anchor_char, scan_char - anchor_char);
					}*/ else if (_stricmp(name, "CHARSET") == 0) {
						if (!data_source->charset)
							data_source->charset=
							(char *)_global_strndup(anchor_char,
							scan_char -
							anchor_char);
					} else if (_stricmp(name, "READTIMEOUT") == 0) {
						if (!data_source->read_timeout)
							data_source->read_timeout=
							(char *)_global_strndup(anchor_char,
							scan_char -
							anchor_char);
					}
					else if (_stricmp(name, "WRITETIMEOUT") == 0) {
						if (!data_source->write_timeout)
							data_source->write_timeout=
							(char *)_global_strndup(anchor_char,
							scan_char -
							anchor_char);
					} else {
						fprintf(stderr, 
							"[%s][%d][ERROR] Unhandled attribute (%s).\n", 
							__FILE__,
							__LINE__, 
							name);
					}

					if (name)
						_global_free(name);
					name = 0;
				}
			}
			break;
		default:
			fprintf(stderr, "[%s][%d][ERROR] Unhandled state.\n", __FILE__, __LINE__);
			return FALSE;
		} /* switch */

		/* is attribute value term ? */
		if (odbc_read_data_source_str_val_term(delim, *scan_char))
			state = OPODBCUTIL_ATTR_PARSE_STATE_NAME_START;

		/* is attribute list term ? */
		if (odbc_read_data_source_str_term(delim, scan_char))
			break;

		scan_char++;

	} /* while scan */

	if (name)
		_global_free(name);

	return TRUE;
}

BOOL odbc_read_data_source_str_term(odbc_delim_t delim, char *scan_char)
{
	switch (delim) {
	case OPODBCUTIL_DELIM_NULL:
		if (scan_char[0] == '\0' && scan_char[1] == '\0')
			return TRUE;
		break;
	case OPODBCUTIL_DELIM_SEMI:
		if (scan_char[0] == '\0')
			return TRUE;
		break;
	case OPODBCUTIL_DELIM_BOTH:
		/* we can not really handle both - so handle as per MYODBCUTIL_DELIM_NULL */
		if (scan_char[0] == '\0' && scan_char[1] == '\0')
			return TRUE;
		break;
	}

	return FALSE;
}

#define OPODBCUtil_WriteDataSourceStr_DELIM  \
	if (idx)                       \
{                                   \
	if (idx >= max_len)        \
	return FALSE;               \
	str[idx] = del;        \
	idx++;                       \
}

/*!
\brief    Formulates a connection string reflecting any user modifications.

The string can be used to pass back to SQLDriverConnect.
*/  
BOOL odbc_write_data_source_str(odbc_data_source_t *data_source, 
								odbc_delim_t delim, 
								char *str,
								SQLSMALLINT max_len)
{
	int idx = 0;
	char del = ';';

	if (max_len < 2)
		return FALSE;

	switch (delim) {
	case OPODBCUTIL_DELIM_NULL:
		del = '\0';
		break;
	case OPODBCUTIL_DELIM_SEMI:
	case OPODBCUTIL_DELIM_BOTH:
		break;
	}

	str[idx] = '\0';

	if (data_source->default_db) {
		if (!odbc_insert_str(str, "DATABASE=", max_len, &idx))
			return FALSE;
		if (!odbc_insert_str(str, data_source->default_db, max_len, &idx))
			return FALSE;
	}
       	if (data_source->dsn_desc) {
		OPODBCUtil_WriteDataSourceStr_DELIM;

		if (!odbc_insert_str(str, "DESCRIPTION=", max_len, &idx))
			return FALSE;
		if (!odbc_insert_str(str, data_source->default_db, max_len, &idx))
			return FALSE;
	} 
	if (data_source->driver_name) {
		OPODBCUtil_WriteDataSourceStr_DELIM;

		if (!odbc_insert_str(str, "DRIVER=", max_len, &idx))
			return FALSE;
		if (!odbc_insert_str(str, data_source->driver_name, max_len, &idx))
			return FALSE;
	}

	if (data_source->dsn_name) {
		OPODBCUtil_WriteDataSourceStr_DELIM;

		if (!odbc_insert_str(str, "DSN=", max_len, &idx))
			return FALSE;
		if (!odbc_insert_str(str, data_source->dsn_name, max_len, &idx))
			return FALSE;
	}

	if (data_source->option) {
		OPODBCUtil_WriteDataSourceStr_DELIM;

		if (!odbc_insert_str(str, "OPTION=", max_len, &idx))
			return FALSE;
		if (!odbc_insert_str(str, data_source->option, max_len, &idx))
			return FALSE;
	}

	if (data_source->passwd) {
		OPODBCUtil_WriteDataSourceStr_DELIM;

		if (!odbc_insert_str(str, "PWD=", max_len, &idx))
			return FALSE;
		if (!odbc_insert_str(str, data_source->passwd, max_len, &idx))
			return FALSE;
	}

	if (data_source->srv_port) {
		OPODBCUtil_WriteDataSourceStr_DELIM;

		if (!odbc_insert_str(str, "PORT=", max_len, &idx))
			return FALSE;
		if (!odbc_insert_str(str, data_source->srv_port, max_len, &idx))
			return FALSE;
	}

	if (data_source->srv_name) {
		OPODBCUtil_WriteDataSourceStr_DELIM;

		if (!odbc_insert_str(str, "SERVER=", max_len, &idx))
			return FALSE;
		if (!odbc_insert_str(str, data_source->srv_name, max_len, &idx))
			return FALSE;
	}

	if (data_source->sock_name) {
		OPODBCUtil_WriteDataSourceStr_DELIM;

		if (!odbc_insert_str(str, "SOCKET=", max_len, &idx))
			return FALSE;
		if (!odbc_insert_str(str, data_source->sock_name, max_len, &idx))
			return FALSE;
	}

	if (data_source->stmt) {
		OPODBCUtil_WriteDataSourceStr_DELIM;

		if (!odbc_insert_str(str, "STMT=", max_len, &idx))
			return FALSE;
		if (!odbc_insert_str(str, data_source->stmt, max_len, &idx))
			return FALSE;
	}

	if (data_source->user_name) {
		OPODBCUtil_WriteDataSourceStr_DELIM;

		if (!odbc_insert_str(str, "UID=", max_len, &idx))
			return FALSE;
		if (!odbc_insert_str(str, data_source->user_name, max_len, &idx))
			return FALSE;
	}
/*
	if (data_source->ssl_cert_auth_file) {
		OPODBCUtil_WriteDataSourceStr_DELIM;

		if (!odbc_insert_str(str, "SSLCA=", max_len, &idx))
			return FALSE;
		if (!odbc_insert_str(str, data_source->ssl_cert_auth_file, max_len, &idx))
			return FALSE;
	}

	if (data_source->ssl_cert_auth_file_path) {
		OPODBCUtil_WriteDataSourceStr_DELIM;

		if (!odbc_insert_str(str, "SSLCAPATH=", max_len, &idx))
			return FALSE;
		if (!odbc_insert_str(str, data_source->ssl_cert_auth_file_path, max_len, &idx))
			return FALSE;
	}

	if (data_source->ssl_cert) {
		OPODBCUtil_WriteDataSourceStr_DELIM;

		if (!odbc_insert_str(str, "SSLCERT=", max_len, &idx))
			return FALSE;
		if (!odbc_insert_str(str, data_source->ssl_cert, max_len, &idx))
			return FALSE;
	}

	if (data_source->ssl_cipher) {
		OPODBCUtil_WriteDataSourceStr_DELIM;

		if (!odbc_insert_str(str, "SSLCIPHER=", max_len, &idx))
			return FALSE;
		if (!odbc_insert_str(str, data_source->ssl_cipher, max_len, &idx))
			return FALSE;
	}

	if (data_source->ssl_key) {
		OPODBCUtil_WriteDataSourceStr_DELIM;

		if (!odbc_insert_str(str, "SSLKEY=", max_len, &idx))
			return FALSE;
		if (!odbc_insert_str(str, data_source->ssl_key, max_len, &idx))
			return FALSE;
	}

	if (data_source->ssl_verify) {
		OPODBCUtil_WriteDataSourceStr_DELIM;

		if (!odbc_insert_str(str, "SSLVERIFY=", max_len, &idx))
			return FALSE;
		if (!odbc_insert_str(str, data_source->ssl_verify, max_len, &idx))
			return FALSE;
	}*/

	if (data_source->charset) {
		OPODBCUtil_WriteDataSourceStr_DELIM;

		if (!odbc_insert_str(str, "CHARSET=", max_len, &idx))
			return FALSE;
		if (!odbc_insert_str(str, data_source->charset, max_len,
			&idx))
			return FALSE;
	}

	if (data_source->read_timeout) {
		OPODBCUtil_WriteDataSourceStr_DELIM;

		if (!odbc_insert_str(str, "READTIMEOUT=", max_len, &idx))
			return FALSE;
		if (!odbc_insert_str(str, data_source->read_timeout, max_len,
			&idx))
			return FALSE;
	}

	if (data_source->write_timeout) {
		OPODBCUtil_WriteDataSourceStr_DELIM;

		if (!odbc_insert_str(str, "WRITETIMEOUT=", max_len, &idx))
			return FALSE;
		if (!odbc_insert_str(str, data_source->write_timeout, max_len,
			&idx))
			return FALSE;
	}

	if (delim == OPODBCUTIL_DELIM_NULL) {
		OPODBCUtil_WriteDataSourceStr_DELIM;
		OPODBCUtil_WriteDataSourceStr_DELIM;
	}

	return TRUE;
}
/*!
\brief    Inserts str_in into str at index.

This is used to build the outgoing connection string for
SQLDriverConnect().
*/  
BOOL odbc_insert_str(char *str, LPCSTR str_in, SQLSMALLINT max_len, int *index)
{
	int str_in_len = strlen(str_in);

	if ((*index + str_in_len + 1) > max_len)
		return FALSE;

	strcpy(&(str[(*index)]), str_in);
	*index += str_in_len;

	return TRUE;
}
